/**
 * Name        : Test1.cpp
 * Author      : Edwin Luis Choquehuanca Mamani
 * Version     : 0.01
 * Copyright   : BSD
 * Description : Exec algorithms
 */

#include <iostream>

#include "strategy/Solver.h"
//#include "strategy/algorithms/Algorithm.h"
#include "strategy/algorithms/MonteCarlo.h"
#include "util/Data.h"
#include "util/UtilData.h"

#include <apr_general.h>
#include <apr_thread_proc.h>
#include <apr_time.h>
#include <apr_thread_mutex.h>

#include <stdlib.h>
#include <assert.h>

/**
 * The number of running threads concurrently
 */
static void* APR_THREAD_FUNC doit(apr_thread_t *thd, void *data);

int main(int argc, const char *argv[]) {
	int seed = 33;
	int NUM_THREADS = atoi(argv[1]);
	const int N = pow((double)2,atoi(argv[2])); //2^20
	const int nExp = atoi(argv[3]);

	//APR variables
	apr_status_t rv;
	apr_pool_t *mp;

    apr_thread_t **thd_arr = (apr_thread_t**)malloc(sizeof(apr_thread_t*)*NUM_THREADS);
	apr_threadattr_t *thd_attr;

	apr_time_t t_begin;
	apr_time_t t_end;

	apr_thread_mutex_t *threadMutex;

	///////////////////////////////////////////////
	apr_initialize();
	apr_pool_create(&mp, NULL);
	apr_threadattr_create(&thd_attr, mp);
	apr_thread_mutex_create(&threadMutex,APR_THREAD_MUTEX_DEFAULT,mp);
	if(atoi(argv[4])==1)
		std::cout<<"#Exp \t"<<"t[us]\t"<<"t[ms]\t"<<"PI"<<"\n";
	int interval = N / NUM_THREADS;
	double *statistics1 = new double[nExp];
	double *statistics2 = new double[nExp];
	for(int test=1;test<=nExp;test++){
		t_begin = apr_time_now();
		double *results = new double[NUM_THREADS];
		double *times_t = new double[NUM_THREADS];
		for (int i = 0; i < NUM_THREADS; i++) {
			Data<int> *data = new Data<int>();
			data->setReferense(results);
			data->index = i;
			data->mutex = threadMutex;
			//data->setData(&seed,0,interval-1);
			data->setData(&seed,0,interval-1, i+test*100);
			data->time_thread = times_t;
			rv = apr_thread_create(&thd_arr[i], thd_attr, doit, (void *)data, mp);
			//assert(rv == APR_SUCCESS);
		}

		for (int i = 0; i < NUM_THREADS; i++) {
			rv = apr_thread_join(&rv, thd_arr[i]);
			//assert(rv == APR_SUCCESS);
		}
		t_end = apr_time_now();

		//statistics
		if(atoi(argv[4])==1){
			std::cout<<test<<"\t"
			<<t_end-t_begin<<"\t"
			<<apr_time_as_msec(t_end-t_begin)<<"\t"
			<<getAverage(results,NUM_THREADS)<<"\n";
		}
		statistics1[test - 1] = (double)(t_end - t_begin);
		statistics2[test - 1] = (double)apr_time_as_msec(t_end-t_begin);
		/*for (int i = 0; i < NUM_THREADS; i++) {
			std::cout<<"Thread "<<i<< " time: "<<times_t[i]<<"\n";
		}*/
		delete(results);
		delete(times_t);
	}
	if(atoi(argv[4])==1){
		std::cout<<"Avg:\t"<<getAverage(statistics1,nExp)<<"\t"<<getAverage(statistics2,nExp)<<"\n";
		std::cout<<"SDev:\t"<<getStdDeviation(statistics1,nExp)<<"\t"<<getStdDeviation(statistics2,nExp)<<"\n";
	}else{
		std::cout<<NUM_THREADS<<"\t"<<getAverage(statistics2,nExp)<<"\n";
	}


	return 0;
}

/**
 * Thread entry point
 */
static void* APR_THREAD_FUNC doit(apr_thread_t *thd, void *data) {
	apr_time_t t_begin_T;
	apr_time_t t_end_T;
	t_begin_T = apr_time_now();
	Data<int> *tmp = (Data<int> *)data;
	double *tmpResults = tmp->getReference();
	double *tmpTimes = tmp->time_thread;
	MonteCarlo *anyAlgor = new MonteCarlo();
	Solver *aSolver = new Solver(anyAlgor);
	double response = aSolver->execAlgorithm(tmp);

	apr_thread_mutex_lock(tmp->mutex);
	tmpResults[tmp->index] = response;
	t_end_T = apr_time_now();
	tmpTimes[tmp->index] = apr_time_as_msec(t_end_T - t_begin_T);
	apr_thread_mutex_unlock(tmp->mutex);

	apr_thread_exit(thd, APR_SUCCESS);
	return NULL;
}

